<?php

/* API接口文件 */

class apiController extends Ue
{
    protected $table = 'kami_list';
    protected $tmpTokenAct = ['getBindUdid', 'reUdid'];//临时TOKEN可操作的接口
    protected $JwtInfo;
    protected $m;

    public $app, $appid, $ver, $admConf, $appConf;

    public function index(){
        if(!U_POST){$_POST = $_GET;}//如果不是POST请求，就吧GET请求传给POST
        if(count($this->gets)<3)$this->out->go(201,'api error');
        $appid = $this->gets[0];
        $ver = $this->gets[1];
        $this->m = $this->gets[2];
        $method = "__".$this->m;
        if(!method_exists($this,$method))$this->out->go(201,"api:{$this->m} error");
        $this->app = $this->__app($appid,$ver);
        $this->out = $this->out->setApp($this->app,$this->m);
        $this->__dataCheck();
        $this->out->key = $_POST['key'];
        $this->Jwt = t('Jwt', $this->app['app_key'], 2592000);

        if ($this->app['app_state'] != 'on') $this->out->go(201, $this->app['app_off_msg']);
        if ($this->app['ver']['ver_state'] == 'off') $this->out->go(101, $this->app['ver']['ver_off_msg']);
        $this->$method();
    }

    //__init 函数会在控制器被创建时自动运行用于初始化工作，如果您要使用它，请按照以下格式编写代码即可：
    public function __init()
    {
        $this->ip = t('ip')->getIp();//获取客户端IP
        $this->out = t('out');
    }

    protected function __dataCheck()
    {
        if ($_POST) {
            $sign = $_POST['sign'] ?? '';
            if ($this->app['api_encrypt'] == 'on') {
                if (empty($_POST['data'])) $this->out->go(126);
                if (empty($this->app['api_key'])) $this->out->go(127);
                $api_key = json_decode($this->app['api_key'], true);
                if (!isset($api_key['private'])) $this->out->go(127);
                $Rsa = t('Rsa');//实例化对象
                $dedata = $Rsa->privateDecrypt($_POST['data'], $api_key['private']);
                if (empty($dedata)) $this->out->go(128);
                unset($_POST['data']);
                $_POST = txtArr($dedata);
                if (empty($_POST['key'])) $this->out->go(201, 'key参数不能为空');
            }

            if ($this->app['api_data_exp'] > 0) {
                if (!isset($_POST['time']) || abs((time() - intval($_POST['time']))) > $this->app['api_data_exp']) $this->out->go(101);
            }

            if ($this->app['api_sign'] == 'on') {
                if (empty($sign) || $sign != arrSign($_POST, $this->app['app_key'])) $this->out->go(100);
            }
        }
    }

    protected function __app($id, $ver)
    {
        $appRes = db('app')->where('id = ?', [$id])->fetch();
        if (!$appRes) $this->out->go(201, "appid error");
        $verRes = db('app_ver')->where('appid = ? and ver_key = ?', [$id, $ver])->fetch();
        if (!$verRes) $this->out->go(201, "ver error");
        unset($verRes['id']);
        unset($verRes['appid']);
        $appRes['ver'] = $verRes;
        return $appRes;
    }

    public function ini()
    {//获取配置
        $conf = [
            'name' => $this->out->conf['APP_NAME'],
            'version' => $this->out->conf['APP_VER'],
            'version_new_url' => $this->out->conf['APP_VER_UPURL'],
            'version_new_msg' => $this->out->conf['APP_VER_UPMSG'],
        ];
        $this->out->data($conf)->go(200, '获取成功');
    }

    public function __logon()
    {//登录
        if (U_POST) {
            $checkRules = [
                'user' => ['kami,email', '10,64', '卡号/邮箱不规范'],
                'password' => ['Password', '5,18', '密码不规范', true],
                'udid' => ['string', '5,32', '机器码不规范']
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);
            startSession($_POST['udid']);
            $fail_time = intval(getSession('time'));//取登录错误到期时间
            if ($fail_time > time()) $this->out->go(201, '由于您登录失败次数过多，当前账号已锁定，请' . ($fail_time - time()) . '秒后重试');//关闭登录
            $km_res = $this->db->where('cardNo = ? or cardEmail = ?', [$_POST['user'], $_POST['user']])->fetch();
            if (!$km_res) {
                $fail_num = intval(getSession('num'));
                $fail_num++;
                if ($fail_num >= 10) {
                    setSession('time', time() + (30 * 60));//冻结30分钟
                } elseif ($fail_num >= 5) {
                    setSession('time', time() + (10 * 60));//冻结10分钟
                } else {
                    setSession('num', $fail_num);
                }
                $this->out->go(104);//卡密不正确
            } else {
                if ($km_res['banExp'] > time()) $this->out->go(105, $km_res['banMsg']);//卡密被禁用
                if (!empty($km_res['cardPwd']) && (!isset($_POST['password']) || empty($_POST['password']))) $this->out->go(102);//卡密密码为空
                if (!empty($km_res['cardPwd']) && $km_res['cardPwd'] != md5($_POST['password'])) {//卡密密码错误
                    $fail_num = intval(getSession('num'));
                    $fail_num++;
                    if ($fail_num >= 10) {
                        setSession('time', time() + (30 * 60));//冻结30分钟
                    } elseif ($fail_num >= 5) {
                        setSession('time', time() + (10 * 60));//冻结10分钟
                    } else {
                        setSession('num', $fail_num);
                    }
                    $this->out->go(103);
                }
                delSession('num');
                if (!empty($km_res['useID'])) $this->out->go(108);//卡密已被充值使用，不可使用
            }
            $state = 'y';
            $login_time = time();
            if ($km_res['useTime'] > 0) {//已经使用过的卡密登录过程
                $clientID_Arr = json_decode($km_res['clientID'], true);
                $found_key = array_search($_POST['udid'], array_column($clientID_Arr, 'udid'));

                if ($found_key !== 0 && !$found_key) {//新设备登录
                    if ($km_res['clientMax'] <= count($clientID_Arr)) {
                        if ($this->out->conf['USER_CLIENT_UNTIE'] == 'n') $this->out->go(121);//登录超限
                        $state = 'n';
                    } else {
                        $clientID_Arr[] = ['udid' => $_POST['udid'], 'time' => time(), 'login_time' => $login_time];
                        $data = ['clientID' => json_encode($clientID_Arr)];
                        $res = $this->db->where('id = ?', [$km_res['id']])->update($data);
                        if (!$res) $this->out->go(201, '登录失败，请重试');
                    }
                } else {// 老设备登录
                    // 迭代数组，找到匹配的udid并修改login_time
                    foreach ($clientID_Arr as &$item) {
                        if ($item['udid'] === $_POST['udid']) {
                            $item['login_time'] = $login_time;
                            break; // 找到匹配的udid后可以停止循环
                        }
                    }
                    $data = ['clientID' => json_encode($clientID_Arr)];
                    $res = $this->db->where('id = ?', [$km_res['id']])->update($data);
                    if (!$res) $this->out->go(201, '登录失败，请重试');
                }
            } else {//新卡密登录过程
                $clientID = json_encode([['udid' => $_POST['udid'], 'time' => time(), 'login_time' => $login_time]]);//绑定机器码
                $up_data = ['vipExp' => time() + $km_res['cardVal'], 'useTime' => time(), 'useIP' => $this->ip, 'clientID' => $clientID];
                $up_res = $this->db->where('id = ?', [$km_res['id']])->update($up_data);
                if (!$up_res) $this->out->go(201, '登录失败，请重试');
            }
            $this->Jwt->setIss($km_res['cardNo'])->setClaim(['uid' => $km_res['id'], 'udid' => $_POST['udid'], 'login_time' => $login_time]);
            $token = $this->Jwt->getToken();
            $vipExp = isset($up_data['vipExp']) ? $up_data['vipExp'] : $km_res['vipExp'];
            $user_info = [
                'id' => $km_res['id'],
                'token' => $token,
                'grade' => $this->__getGrade($km_res['cardVal']),
                'kami' => $km_res['cardNo'],
                'note' => $km_res['cardNote'],
                'email' => $km_res['cardEmail'],
                'vipExpDate' => $vipExp != 9999999999 ? date("Y-m-d H:i:s", $vipExp) : '永久',
                'vipExpTime' => $vipExp,
                'state' => $state
            ];
            $this->__log(U_M, $km_res['id']);//记录日志
            $this->out->data($user_info)->go(200, '登录成功');
        }
    }

    public function __verif()
    {//验证
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范']
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_info = $this->__TokenCheck();
            $JwtInfo = $this->Jwt->getClaim();
            if ($this->app['app_mode'] == 'y') {
                if ($km_info['vipExp'] < time()) $this->out->go(106);
            }
            $login_time = time();
            // 迭代数组，找到匹配的udid并修改login_time
            $clientID_Arr = json_decode($km_info['clientID'], true);
            foreach ($clientID_Arr as &$item) {
                if ($item['udid'] === $JwtInfo['udid']) {
                    $item['login_time'] = $login_time;
                    break; // 找到匹配的udid后可以停止循环
                }
            }
            $data = ['clientID' => json_encode($clientID_Arr)];
            $res = $this->db->where('id = ?', [$km_info['id']])->update($data);
            if (!$res) $this->out->go(201, '验证失败');
            $this->Jwt->setIss($km_info['cardNo'])->setClaim(['uid' => $km_info['id'], 'udid' => $JwtInfo['udid'], 'login_time' => $login_time]);
            $token = $this->Jwt->getToken();
            $data = [
                'token'=>$token
            ];
            $this->out->data($data)->go(200, '验证成功');
        }
    }


    public function topup()
    {//充值
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范'],
                'kamiNo' => ['kami', '10,64', '卡密不规范不规范'],
                'kamiPwd' => ['Password', '5,18', '密码不规范', true],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_info = $this->__TokenCheck();
            if ($km_info['vipExp'] >= 9999999999) $this->out->go(999);

            $km_res = $this->db->where('cardNo = ?', [$_POST['kamiNo']])->fetch('id,cardEmail,cardVal,vipExp,useID,useTime,banExp,banMsg,cardPwd');
            if (!$km_res) $this->out->go(104);

            if (!empty($km_res['cardEmail'])) $this->out->go(112);//被充值的卡密已绑定邮箱，不可被充值
            if (!empty($km_res['cardPwd']) && (!isset($_POST['kamiPwd']) || empty($_POST['kamiPwd']))) $this->out->go(102);//卡密密码为空
            if (!empty($km_res['cardPwd']) && $km_res['cardPwd'] != md5($_POST['kamiPwd'])) $this->out->go(103);//卡密密码错误
            if ($km_res['banExp'] > time()) $this->out->go(105, $km_res['banMsg']);//被充值的卡密被禁用

            if (!empty($km_res['useID'])) $this->out->go(108);//卡密已被充值使用，不可使用
            $km_updata = ['useID' => $km_info['id']];
            if ($km_res['useTime'] > 0) {//被登录过的卡密
                if ($km_res['vipExp'] < time()) $this->out->go(109);//卡密已过期
                $vipVal = time() - $km_res['vipExp'];
            } else {
                $km_updata['useTime'] = time();
                $vipVal = $km_res['cardVal'];
            }

            $updata = ['cardVal' => $km_info['cardVal'] + $km_res['cardVal']];
            if ($km_info['vipExp'] < time()) {//当前登录卡密已过期
                $updata['vipExp'] = time() + $vipVal;
            } else {
                $updata['vipExp'] = $km_info['vipExp'] + $vipVal;
            }

            $this->db->beginTransaction();//开启事务
            $km_up_res = $this->db->where('id = ?', [$km_res['id']])->update($km_updata);//更新被充值的卡密数据
            $up_res = $this->db->where('id = ?', [$km_info['id']])->update($updata);
            if ($up_res && $km_up_res) {//因为操作了两个人的数据，所以必须大于等于2，确保一定成功
                $this->db->commit();//进行提交
                $this->__log(U_M, $km_info['id']);//记录日志
                $this->out->go(200, '充值成功');
            } else {
                $km_db->rollback();//回滚
                $this->db->__log(U_M, $km_info['id'], 201);//记录日志
                $this->out->go(201, '充值失败，请重试');
            }
        }
    }

    public function setPwd()
    {//设置密码
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范'],
                'password' => ['Password', '5,18', '密码不规范'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_info = $this->__TokenCheck();
            if (!empty($km_info['cardPwd'])) $this->out->go(110);//已经设置过密码

            $up_res = $this->db->where('id = ?', [$km_info['id']])->update(['cardPwd' => md5($_POST['password'])]);
            if (!$up_res) {
                $this->__log(U_M, $km_info['id'], 201);//记录日志
                $this->out->go(201, '密码设置失败');
            }
            $this->__log(U_M, $km_info['id']);//记录日志
            $this->out->go(200, '密码设置成功');

        }
    }

    public function alterPwd()
    {//更改密码
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范'],
                'password' => ['Password', '5,18', '密码不规范'],
                'newpassword' => ['Password', '5,18', '密码不规范'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_info = $this->__TokenCheck();
            if (empty($km_info['cardPwd'])) $this->out->go(111);//请先设置密码
            if ($km_info['cardPwd'] != md5($_POST['password'])) $this->out->go(103, '当前密码错误');

            $up_res = $this->db->where('id = ?', [$km_info['id']])->update(['cardPwd' => md5($_POST['newpassword'])]);
            if (!$up_res) {
                $this->__log(U_M, $km_info['id'], 201);//记录日志
                $this->out->go(201, '密码修改失败');
            }
            $this->__log(U_M, $km_info['id']);//记录日志
            $this->out->go(200, '密码修改成功');
        }
    }

    public function getBindUdid()
    {//获取已绑定设备
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_info = $this->__TokenCheck();
            $clientID_Arr = json_decode($km_info['clientID'], true);

            $this->out->data($clientID_Arr)->go(200, '获取成功');
        }
    }

    public function reUdid()
    {//换绑设备
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范'],
                'newudid' => ['string', '5,32', '现机器码不规范'],
                'origudid' => ['string', '5,32', '原机器码不规范'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            if ($this->out->conf['USER_CLIENT_UNTIE'] == 'n') $this->out->go(123);//当前系统暂不支持自行解绑操作
            $km_info = $this->__TokenCheck();

            $clientID_Arr = json_decode($km_info['clientID'], true);

            $find = false;
            $client = [];
            foreach ($clientID_Arr as $rows) {
                if ($rows['udid'] == $_POST['origudid']) {
                    if (($rows['time'] + $this->out->conf['USER_CLIENT_UNTIE_CYCLE']) > time()) $this->out->go(124);//当前设备处于系统保护期，暂时无法解绑
                    $find = true;
                } else {
                    $client[] = ['udid' => $rows['udid'], 'time' => $rows['time']];
                }
            }
            if ($find) {
                $client[] = ['udid' => $_POST['newudid'], 'time' => time()];
            } else {
                $this->out->go(201, '解绑设备不存在');
            }

            $data = ['clientID' => json_encode($client)];
            if ($km_info['vipExp'] < 9999999999 && $km_info['vipExp'] > time()) {
                $data['vipExp'] = ($km_info['vipExp'] + $this->out->conf['USER_CLIENT_UNTIE_PUNISH']) > time() ? ($km_info['vipExp'] + $this->out->conf['USER_CLIENT_UNTIE_PUNISH']) : time();
            }

            $res = $this->db->where('id = ?', [$km_info['id']])->update($data);
            if (!$res) $this->out->go(201, '设备换绑失败');

            $this->__log(U_M, $km_info['id']);//记录日志
            $this->out->go(200, '设备换绑成功');

        }
    }

    public function getInfo()
    {//获取信息
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_info = $this->__TokenCheck();

            $user_info = [
                'id' => $km_info['id'],
                'token' => $_POST['token'],
                'grade' => $this->__getGrade($km_info['cardVal']),
                'kami' => $km_info['cardNo'],
                'note' => $km_info['cardNote'],
                'email' => $km_info['cardEmail'],
                'vipExpDate' => $km_info['vipExp'] != 9999999999 ? date("Y-m-d H:i:s", $km_info['vipExp']) : '永久',
                'vipExpTime' => $km_info['vipExp'],
                'state' => 'y'
            ];
            $this->out->data($user_info)->go(200, '获取成功');

        }
    }

    public function bindEmail()
    {//绑定邮箱
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范'],
                'email' => ['email', '5,64', '绑定邮箱不规范'],
                'code' => ['int', '4,10', '验证码填写有误'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_info = $this->__TokenCheck();

            $dtime = time() - (60 * $this->out->conf['CRC_EXP']);//验证码有效期
            $vcode_db = db('vcode');
            $res_vcode = $vcode_db->where('receiveAn = ? and code = ? and type = ? and state = ? and time > ?', [$_POST['email'], $_POST['code'], U_M, 'y', $dtime])->update(['state' => 'n']);
            if (!$res_vcode || $vcode_db->rowCount() < 1) $this->out->go(117);

            $up_res = $this->db->where('id = ?', [$km_info['id']])->update(['cardEmail' => $_POST['email']]);
            if (!$up_res) $this->out->go(201, '绑定失败');
            $this->__log(U_M, $km_info['id']);//记录日志
            $this->out->go(200, '绑定成功');
        }
    }

    public function untieEmail()
    {//解绑邮箱
        if (U_POST) {
            $checkRules = [
                'token' => ['Jwt', '', 'TOKEN令牌不规范'],
                'email' => ['email', '5,64', '解绑邮箱不规范'],
                'code' => ['int', '4,10', '验证码填写有误'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_info = $this->__TokenCheck();

            if ($km_info['cardEmail'] != $_POST['email']) $this->out->go(119);//当前卡密邮箱与解绑的邮箱不匹配

            $dtime = time() - (60 * $this->out->conf['CRC_EXP']);//验证码有效期
            $vcode_db = db('vcode');
            $res_vcode = $vcode_db->where('receiveAn = ? and code = ? and type = ? and state = ? and time > ?', [$_POST['email'], $_POST['code'], U_M, 'y', $dtime])->update(['state' => 'n']);
            if (!$res_vcode || $vcode_db->rowCount() < 1) $this->out->go(117);

            $up_res = $this->db->where('id = ?', [$km_info['id']])->update(['cardEmail' => NULL]);
            if (!$up_res) $this->out->go(201, '解绑失败');
            $this->__log(U_M, $km_info['id']);//记录日志
            $this->out->go(200, '解绑成功');
        }
    }

    public function rePwd()
    {//重置密码
        if (U_POST) {
            $checkRules = [
                'email' => ['email', '5,64', '解绑邮箱不规范'],
                'code' => ['int', '4,10', '验证码填写有误'],
                'password' => ['Password', '5,18', '密码不规范'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            $km_res = $this->db->where('cardEmail = ?', [$_POST['email']])->fetch('id,banExp,banMsg,useID');
            if (!$km_res) $this->out->go(120);

            if ($km_res['banExp'] > time()) $this->out->go(105, $km_res['banMsg']);//卡密被禁用
            if (!empty($km_res['useID'])) $this->out->go(108);//卡密已被充值使用，不可使用

            $dtime = time() - (60 * $this->out->conf['CRC_EXP']);//验证码有效期
            $vcode_db = db('vcode');
            $res_vcode = $vcode_db->where('receiveAn = ? and code = ? and type = ? and state = ? and time > ?', [$_POST['email'], $_POST['code'], U_M, 'y', $dtime])->update(['state' => 'n']);
            if (!$res_vcode || $vcode_db->rowCount() < 1) $this->out->go(117);

            $up_res = $this->db->where('id = ?', [$km_res['id']])->update(['cardPwd' => md5($_POST['password'])]);
            if (!$up_res) $this->out->go(201, '密码重置失败');
            $this->__log(U_M, $km_res['id']);//记录日志
            $this->out->go(200, '密码重置成功');
        }
    }

    public function getVcode()
    {//获取验证码
        if (U_POST) {
            $checkRules = [
                'receiveAn' => ['email', '5,64', '收信邮箱不规范'],
                'type' => ['sameone', 'bindEmail,untieEmail,rePwd', '取验证码类型有误'],
                'sign' => ['string', '32,32', '数据签名不规范', $this->out->conf['API_SIGN'] == 'on' ? false : true],
                'time' => ['int', '10,10', '客户端时间获取错误', $this->out->conf['API_DATA_EXP'] > 0 ? false : true],
            ];
            $dataChecker = t('dataChecker', $_POST, $checkRules);
            $res = $dataChecker->check();
            if (!$res) $this->out->go(201, $dataChecker->error);

            if (empty($this->out->conf['SMTP_HOST']) || empty($this->out->conf['SMTP_USER']) || empty($this->out->conf['SMTP_PASS']) || empty($this->out->conf['SMTP_PORT'])) $this->out->go(115);//未配置发信功能

            $vcdb = db('vcode');
            $stime = timeRange();
            $etime = timeRange(0, 1);
            $vcode_res = $vcdb->where('receiveAn = ?', [$_POST['receiveAn']])->fetch('count(case when time> ' . (time() - 120) . ' then 1 end) as vyes,count(case when time> ' . $stime . ' and time< ' . $etime . ' then 1 end) as vnum');//统计收信账号频繁限制
            if ($vcode_res && ($vcode_res['vyes'] > 0 || $vcode_res['vnum'] >= 10)) $this->out->go(114);//验证码频繁

            $code = substr(str_shuffle('1234567890'), 0, $this->out->conf['CRC_LENGTH']);//生成验证码
            $add = ['receiveAn' => $_POST['receiveAn'], 'type' => $_POST['type'], 'code' => $code, 'time' => time(), 'ip' => $this->ip];

            $km_res = $this->db->where('cardEmail = ?', [$_POST['receiveAn']])->fetch();
            switch ($_POST['type']) {
                case 'rePwd'://重置密码
                    if (!$km_res) $this->out->go(201, '当前邮箱账号未绑定卡密');
                    if ($km_res['banExp'] > time()) $this->out->go(105, $logon_res['banMsg']);//卡密已被禁用
                    $add['uid'] = $km_res['id'];
                    $title = $this->out->conf['APP_NAME'] . '重置密码';
                    $content = '重置密码验证码：' . $code . '，' . $this->out->conf['CRC_EXP'] . '分钟内有效，请不要把验证码泄露给其他人【' . $this->out->conf['APP_NAME'] . '】';
                    break;
                case 'bindEmail'://绑定账号
                    if ($km_res) $this->out->go(201, '当前邮箱账号已绑定其他卡密');
                    $title = $this->out->conf['APP_NAME'] . '绑定账号';
                    $content = '绑定账号验证码：' . $code . '，' . $this->out->conf['CRC_EXP'] . '分钟内有效，请不要把验证码泄露给其他人【' . $this->out->conf['APP_NAME'] . '】';
                    break;
                case 'untieEmail'://解绑账号
                    if (!$km_res) $this->out->go(201, '当前邮箱账号未绑定卡密');
                    if ($km_res['banExp'] > time()) $this->out->go(105, $logon_res['banMsg']);//卡密已被禁用
                    $add['uid'] = $km_res['id'];
                    $title = $this->out->conf['APP_NAME'] . '解绑账号';
                    $content = '解绑账号验证码：' . $code . '，' . $this->out->conf['CRC_EXP'] . '分钟内有效，请不要把验证码泄露给其他人【' . $this->out->conf['APP_NAME'] . '】';
                    break;
            }

            $add_res = $vcdb->add($add);
            if (!$add_res) $this->out->go(201, '验证码获取失败');//验证码发送失败
            $email = t('mailer');
            $mail_Config = [
                'Host' => $this->out->conf['SMTP_HOST'],
                'Port' => $this->out->conf['SMTP_PORT'],
                'FromName' => $this->out->conf['APP_NAME'],
                'Username' => $this->out->conf['SMTP_USER'],
                'Password' => $this->out->conf['SMTP_PASS']
            ];
            $res = $email->send($mail_Config, $_POST['receiveAn'], $title, $content);
            if ($res) {
                $this->out->go(200, '邮件发送成功，请留意您的邮箱');
            }
            $this->out->go(201, '邮件发送失败，请重试');
        }
    }

    protected function __TokenCheck()
    {//Token检查
        $verifyResult = $this->Jwt->verifyToken($_POST['token']);
        if (!$verifyResult) $this->out->go(106);

        $JwtInfo = $this->Jwt->getClaim();
        if (!$JwtInfo || !isset($JwtInfo['uid']) || !isset($JwtInfo['udid'])) $this->out->go(107);

        $km_res = $this->db->where('id = ?', [$JwtInfo['uid']])->fetch();
        if (!$km_res) $this->out->go(104);//卡密不存在

        $clientID_Arr = json_decode($km_res['clientID'], true);
        $found_key = array_search($JwtInfo['udid'], array_column($clientID_Arr, 'udid'));

        if ($found_key !== 0 && !$found_key && !in_array(U_M, $this->tmpTokenAct)) $this->out->go(121, '登录设备超限，请解绑已登录设备后操作');//登录设备信息不匹配
        if ($km_res['banExp'] > time()) $this->out->go(105, $km_res['banMsg']);//卡密被禁用
        if (!empty($km_res['useID'])) $this->out->go(108);//卡密已被充值使用，不可使用

        // TODO 数据库记录上一次心跳时间，和当前时间做比较，超出“心跳有效期”就返回验证失败。
        // 验证当前token登录时间和数据库登录时间是否一致
        $current_udid = null;
        foreach ($clientID_Arr as &$item) {
            if ($item['udid'] === $JwtInfo['udid']) {
                $current_udid = $item;
                break;
            }
        }
        if ($current_udid['login_time'] != $JwtInfo['login_time']) $this->out->go(125);//令牌登录时间和数据库登录时间不匹配

        return $km_res;
    }

    protected function __log($type, $uid, $code = 200)
    {//记录日志
        if ($this->out->conf['APP_USER_LOG'] == 'on') {
            db('logs')->add(['uid' => $uid, 'type' => $type, 'time' => time(), 'ip' => $this->ip, 'code' => $code]);
        }//记录日志
    }

    protected function __getGrade($val)
    {//记录日志
        $k = $val >= 9999999999 ? 'long' : ($val >= 31536000 ? 'n' : ($val >= 15552000 ? 'b' : ($val >= 7776000 ? 'j' : ($val >= 2592000 ? 'y' : ($val >= 604800 ? 'z' : ($val >= 86400 ? 'd' : ($val >= 3600 ? 'h' : ($val >= 60 ? 'i' : 's'))))))));
        $Grade = ['s' => '秒卡', 'i' => '分钟卡', 'h' => '小时卡', 'd' => '天卡', 'z' => '周卡', 'y' => '月卡', 'j' => '季卡', 'b' => '半年卡', 'n' => '年卡', 'long' => '永久卡'];//卡密等级
        return $Grade[$k];
    }
}
